package oop.ex3.filemanager.actions;

import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

import oop.ex3.messages.*;
import oop.ex3.messages.exception.InvalidHeaderException;
import oop.ex3.messages.exception.UnexpectedMessageEndException;
import oop.ex3.nameserver.exception.UnexpectedMessageException;

import oop.ex3.filemanager.FMContext;
import oop.ex3.filemanager.FileInfo;
import oop.ex3.filemanager.actions.exceptions.InvalidNumOfParamsException;
import oop.ex3.filemanager.actions.exceptions.RecievedError;
import oop.ex3.sockets.ClientSocket;
import oop.ex3.sockets.FMSocket;

public class SearchFile extends FmAction {

	private static final int FILE_PLACE = 1;
	private static final int NUM_OF_PARAMS = 2;

	/**
	 * constructor for action
	 * @param context
	 */
	public SearchFile(FMContext context) {
		super(context);
		}

	/**
	 * search wanted file
	 * @throws InvalidNumOfParamsException 
	 */
	public void run(String[] params) throws InvalidNumOfParamsException {
		if (params.length != NUM_OF_PARAMS){
			throw new InvalidNumOfParamsException();
		}
		if (_context.containsFile(params[FILE_PLACE])){
			System.out.println("File already exists");
			return;
		}
		InetSocketAddress fm;
		if ((fm = findFile(_context.getServersSet(), params[FILE_PLACE]))
				!= null){
			System.out.println("File Downloaded Successfully from " +
					fm.getHostName()+":"+fm.getPort());
		} else {
			System.out.println("Downloading failed");
		}
		
	}
	
	/**
	 * helper method for searching file
	 * @param servers - servers to ask file from
	 * @param file wanted file
	 * @return true if found the file, false if not
	 */
	private InetSocketAddress findFile(LinkedHashSet<InetSocketAddress> servers, 
			String file){
		ClientSocket s = null;
		FMSocket fMsocket = null;
		_context.addServers(servers);
		if (servers.isEmpty()){
			return null;
		}
		//ask for FMs
		for (InetSocketAddress server : servers){
			try {
				s = new ClientSocket(new InetSocketAddress
						(server.getAddress(),server.getPort()));
				performHandShake(s);
				s.SendMessage(new FileMessage(ValidHeaders.WANTFILE, file));
				HashSet<InetSocketAddress> fms = 
					new HashSet<InetSocketAddress>();
				MessageInterface msg;
				try {
					while (!(msg = s.readMessage()).getHeader().
							equals(ValidHeaders.ENDLIST) && !msg.getHeader().
							equals(ValidHeaders.ERROR)){
						//TODO- if error- should i check the other FMs?
						System.out.println(((PeerInfoMessage)msg).getPeer().getPort());
						fms.add(((PeerInfoMessage)msg).getPeer());
					}
					finishSession(s);
					s.closeClientSocket();
				} catch (Exception e){
					//TODO send endsession?
					if (s != null){
						s.closeClientSocket();
					} 
					//can't read message - try read next one
				}
				
				//request file from fm
				for (InetSocketAddress fm : fms){
					try {
						//TODO if it's null? no problem?
						fMsocket = new FMSocket(new InetSocketAddress
								(fm.getAddress(),fm.getPort()));
						fMsocket.SendMessage(new FileMessage
								(ValidHeaders.WANTFILE, file));
						if ((msg = fMsocket.readMessage()).getHeader().equals
								(ValidHeaders.FILE)){
							File wantedFile = new File(_context.getDir()
									+File.separator+file);
							fMsocket.copyFile((FileContentMessage)msg, 
									wantedFile);
							System.out.println("going to close");
							fMsocket.closeClientSocket();
							_context.addFile(file, new FileInfo
									(wantedFile.getName(), _context.getDir()));
							return fm;
						}	fMsocket.closeClientSocket();
					} catch (Exception e){
						e.printStackTrace();
						if (fMsocket != null){
							fMsocket.closeClientSocket();
						}
					}
				}
			} catch (IOException e) {
				if (fMsocket != null){
						fMsocket.closeClientSocket();
				}
				if (s != null){
					s.closeClientSocket();
				} 
				//couldnt connect to NS? try next one
			} catch (InvalidHeaderException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (UnexpectedMessageEndException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (UnexpectedMessageException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (RecievedError e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		LinkedHashSet<InetSocketAddress> newServers = 
			new LinkedHashSet<InetSocketAddress>();
		for (InetSocketAddress server : servers){
			try {
				s = new ClientSocket(new InetSocketAddress
						(server.getAddress(),server.getPort()));
				performHandShake(s);
				s.SendMessage(new SimpleMessage(ValidHeaders.WANTSERVERS));
				MessageInterface msg;
				while (!(msg = s.readMessage()).getHeader().
						equals(ValidHeaders.ENDLIST) && !msg.getHeader().
						equals(ValidHeaders.ERROR)){
					//TODO- if error- should i check the other FMs?
					if (!_context.getServersSet().contains
							(((PeerInfoMessage)msg).getPeer())){
						newServers.add(((PeerInfoMessage)msg).getPeer());
					}
				}
				finishSession(s);
				s.closeClientSocket();
				
			} catch (Exception e){
				//TODO do something!!
			}
		} return findFile(newServers, file);
	} 

}
